import sys
from PySide6.QtCore import Qt, QAbstractTableModel, QModelIndex, QMimeData
from PySide6.QtWidgets import (
    QApplication, QTableView, QWidget, QVBoxLayout, QStyleOptionViewItem
)
from PySide6 import QtCore, QtGui, QtWidgets
from PySide6.QtGui import QPixmap

# ---------- 1. 模型 ----------
class TableModel(QAbstractTableModel):
    def __init__(self, data, parent=None):
        super().__init__(parent)
        self._data = data

    def rowCount(self, parent=QModelIndex()):
        return len(self._data)

    def columnCount(self, parent=QModelIndex()):
        return len(self._data[0]) if self._data else 0

    def data(self, index, role=Qt.DisplayRole):
        if role in (Qt.DisplayRole, Qt.EditRole):
            return self._data[index.row()][index.column()]
        return None

    def setData(self, index, value, role=Qt.EditRole):
        if role == Qt.EditRole and index.isValid():
            self._data[index.row()][index.column()] = value
            self.dataChanged.emit(index, index)
            return True
        return False

    def flags(self, index):
        return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled


# ---------- 2. 视图 ----------
class DragTable(QTableView):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setDragDropMode(QTableView.DragDrop)   # 关键
        self.setDragDropMode(QTableView.InternalMove)
        self.setSelectionMode(QTableView.SingleSelection)
        
        self.setSelectionBehavior(QTableView.SelectItems)
    def dragEnterEvent(self, event):
        print("dragEnterEvent")
        event.acceptProposedAction()
    def dragMoveEvent(self, event):
        print("dragMoveEvent")
        if self.indexAt(event.position().toPoint()).isValid():
            event.acceptProposedAction()   # 允许
        else:
            event.ignore()
    # 用 text/plain 传文字，最简单
    def startDrag(self, supportedActions):
        print("startDrag 被调用")
        idx = self.currentIndex()
        if not idx.isValid():
            return
        text = idx.data(Qt.DisplayRole)
        mime = QMimeData()
        mime.setText(text)
        drag = QtGui.QDrag(self)
        drag.setPixmap(QPixmap(1, 1))
        drag.setMimeData(mime)
        drag.exec(Qt.MoveAction)

    # 放下时交换
    def dropEvent(self, event):
        print("dropEvent")
        src_text = event.mimeData().text()
        dst = self.indexAt(event.position().toPoint())
        if not dst.isValid() or src_text == dst.data(Qt.DisplayRole):
            event.ignore()
            return
        # 交换
        model = self.model()
        dst_text = dst.data(Qt.DisplayRole)
        model.setData(dst, src_text)
        # 把源格子也改掉
        src = self.currentIndex()
        model.setData(src, dst_text)
        event.acceptProposedAction()
    


# ---------- 3. 窗口 ----------
class Demo(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("拖拽交换单元格（100% 可跑）")
        self.resize(400, 300)

        data = [[f"{r}{c}" for c in range(4)] for r in range(5)]
        view = DragTable()
        view.setModel(TableModel(data))

        QVBoxLayout(self).addWidget(view)


# ---------- 4. 运行 ----------
if __name__ == "__main__":
    app = QApplication(sys.argv)
    d = Demo()
    d.show()
    app.exec()